﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using GrommetXBee = Grommet.Net.XBee;

namespace SoCGadget.Robotics.XBee
{
    public class GrommetXBeeModule : IXBeeModule
    {
        #region IXBeeModule

        public event EventHandler<DataSampleReceivedArgs> DataSampleReceived = (sender, args) => { };

        public event SerialErrorReceivedEventHandler SerialPortErrorReceived = (sender, args) => { };

        public string SerialPortName
        {
            get { return _xbee.SerialPort.PortName; }
        }

        public bool SerialPortIsOpen
        {
            get { return _xbee.SerialPort.IsOpen; }
        }

        public void Open()
        {
            _xbee.Open();
        }

        public void Close()
        {
            _xbee.Close();
        }

        public byte SendCommand(string command, Action<CommandStatus> callback)
        {
            return _xbee.SendCommand(_commands[command.ToUpperInvariant()], frame => callback(CommandStatus.Success));
        }

        public byte SendCommand(string command, byte value, Action<CommandStatus> callback)
        {
            return _xbee.SendCommand(_commands[command.ToUpperInvariant()],
                              frame => callback(CommandStatus.Success),
                              value);
        }

        public byte SendCommand(string command, ushort value, Action<CommandStatus> callback)
        {
            return _xbee.SendCommand(_commands[command.ToUpperInvariant()],
                              frame => callback(CommandStatus.Success),
                              value);
        }

        public byte SendCommand(string command, uint value, Action<CommandStatus> callback)
        {
            return _xbee.SendCommand(_commands[command.ToUpperInvariant()],
                              frame => callback(CommandStatus.Success),
                              value);
        }

        public byte SendCommand(string command, string value, Action<CommandStatus> callback)
        {
            return _xbee.SendCommand(_commands[command.ToUpperInvariant()],
                              frame => callback(CommandStatus.Success),
                              value);
        }

        public byte SendString(ulong destinationSerialNumber, ushort destinationAddress, string message, Action<CommandStatus> callback)
        {
            return _xbee.SendString(destinationSerialNumber, destinationAddress, message, frame =>
            {
                var txStatus = frame as GrommetXBee.ZigBeeTransmitStatus;
                if (txStatus != null)
                {
                    callback(FromGrommetDeliveryStatus(txStatus.DeliveryStatus));
                }
                callback(CommandStatus.Success);
            });
        }

        public byte SendData(ulong destinationSerialNumber, ushort destinationAddress, byte[] data, Action<CommandStatus> callback)
        {
            return _xbee.SendData(destinationSerialNumber, destinationAddress, data, frame =>
            {
                var txStatus = frame as GrommetXBee.ZigBeeTransmitStatus;
                if (txStatus != null)
                {
                    callback(FromGrommetDeliveryStatus(txStatus.DeliveryStatus));
                }
                callback(CommandStatus.Success);
            });
        }

        public byte SendRemoteCommand(ulong destinationSerialNumber, ushort destinationAddress, string command, byte value, Action<CommandStatus> callback)
        {
            return _xbee.SendRemoteCommand(destinationSerialNumber, destinationAddress, _commands[command.ToUpperInvariant()], value, frame =>
            {
                var txStatus = frame as GrommetXBee.RemoteCommandResponse;
                if (txStatus != null)
                {
                    callback(FromGrommetRemoteCommandStatus(txStatus.RemoteCommandStatus));
                }
                callback(CommandStatus.Success);
            });
        }

        public byte SendRemoteCommand(ulong destinationSerialNumber, ushort destinationAddress, string command, ushort value, Action<CommandStatus> callback)
        {
            return _xbee.SendRemoteCommand(destinationSerialNumber, destinationAddress, _commands[command.ToUpperInvariant()], value, frame =>
            {
                var txStatus = frame as GrommetXBee.RemoteCommandResponse;
                if (txStatus != null)
                {
                    callback(FromGrommetRemoteCommandStatus(txStatus.RemoteCommandStatus));
                }
                callback(CommandStatus.Success);
            });
        }

        public byte SendRemoteCommand(ulong destinationSerialNumber, ushort destinationAddress, string command, uint value, Action<CommandStatus> callback)
        {
            return _xbee.SendRemoteCommand(destinationSerialNumber, destinationAddress, _commands[command.ToUpperInvariant()], value, frame =>
            {
                var txStatus = frame as GrommetXBee.RemoteCommandResponse;
                if (txStatus != null)
                {
                    callback(FromGrommetRemoteCommandStatus(txStatus.RemoteCommandStatus));
                }
                callback(CommandStatus.Success);
            });
        }

        #endregion

        public GrommetXBeeModule(string portName, int baud, string apiMode)
        {
            var apiModeEnum = (GrommetXBee.ApiMode)Enum.Parse(typeof(GrommetXBee.ApiMode), apiMode, true);

            _xbee = new GrommetXBee.XBeeModule(new SerialPort(portName, baud), apiModeEnum);
            _xbee.DataSampleReceived += Xbee_DataSampleReceived;
            _xbee.SerialPort.ErrorReceived += SerialPort_ErrorReceived;
            LoadCommandData();
        }

        private void Xbee_DataSampleReceived(object sender, GrommetXBee.IODataSampleReceivedArgs e)
        {
            DataSampleReceived(this, new DataSampleReceivedArgs(e.SourceSerialNumber, e.SourceAddress, e.DigitalChannelMask, e.DigitalSample, e.AnalogChannelMask, e.AnalogSamples));
        }

        private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            SerialPortErrorReceived(this, e);
        }

        private CommandStatus FromGrommetDeliveryStatus(GrommetXBee.DeliveryStatus status)
        {
            switch (status)
            {
                case GrommetXBee.DeliveryStatus.Success:
                    return CommandStatus.Success;
                case GrommetXBee.DeliveryStatus.ClearChannelAssessmentFailure:
                    return CommandStatus.ClearChannelAssessmentFailure;
                case GrommetXBee.DeliveryStatus.InvalidDestinationEndpoint:
                    return CommandStatus.InvalidDestinationEndpoint;
                case GrommetXBee.DeliveryStatus.NetworkAckFailure:
                    return CommandStatus.NetworkAckFailure;
                case GrommetXBee.DeliveryStatus.NotJoinedToNetwork:
                    return CommandStatus.NotJoinedToNetwork;
                case GrommetXBee.DeliveryStatus.SelfAddressed:
                    return CommandStatus.SelfAddressed;
                case GrommetXBee.DeliveryStatus.AddressNotFound:
                    return CommandStatus.AddressNotFound;
                case GrommetXBee.DeliveryStatus.RouteNotFound:
                    return CommandStatus.RouteNotFound;
            }
            return CommandStatus.Unknown;
        }

        private CommandStatus FromGrommetRemoteCommandStatus(GrommetXBee.RemoteCommandStatus status)
        {
            switch (status)
            {
                case GrommetXBee.RemoteCommandStatus.OK:
                    return CommandStatus.Success;
                case GrommetXBee.RemoteCommandStatus.Error:
                    return CommandStatus.Error;
                case GrommetXBee.RemoteCommandStatus.InvalidCommand:
                    return CommandStatus.InvalidCommand;
                case GrommetXBee.RemoteCommandStatus.InvalidParameter:
                    return CommandStatus.InvalidParameter;
            }
            return CommandStatus.Unknown;
        }

        private void LoadCommandData()
        {
            _commands = new Dictionary<string, GrommetXBee.XBeeCommand>
            {
                {"WR", GrommetXBee.XBeeCommand.Write},
                {"RE", GrommetXBee.XBeeCommand.RestoreDefaults},
                {"FR", GrommetXBee.XBeeCommand.SoftwareReset},
                {"NR", GrommetXBee.XBeeCommand.NetworkReset},
                {"MY", GrommetXBee.XBeeCommand.Address},
                {"MP", GrommetXBee.XBeeCommand.ParentAddress},
                {"NC", GrommetXBee.XBeeCommand.NumberOfRemainingChildrean},
                {"SH", GrommetXBee.XBeeCommand.SerialNumberHigh},
                {"SL", GrommetXBee.XBeeCommand.SerialNumberLow},
                {"NI", GrommetXBee.XBeeCommand.NodeIdentifier},
                {"DD", GrommetXBee.XBeeCommand.DeviceTypeIdentifier},
                {"NP", GrommetXBee.XBeeCommand.MaximumRFPayloadBytes},
                {"CH", GrommetXBee.XBeeCommand.OperatingChannel},
                {"ID", GrommetXBee.XBeeCommand.ExtendedPanID},
                {"OP", GrommetXBee.XBeeCommand.OperatingExtendedPanID},
                {"NH", GrommetXBee.XBeeCommand.MaximumUnicastHops},
                {"BH", GrommetXBee.XBeeCommand.BroadcastHops},
                {"OI", GrommetXBee.XBeeCommand.OperatingPanID},
                {"NT", GrommetXBee.XBeeCommand.NodeDiscoverTimeout},
                {"NO", GrommetXBee.XBeeCommand.NetworkDiscoveryOptions},
                {"ND", GrommetXBee.XBeeCommand.NodeDiscover},
                {"DN", GrommetXBee.XBeeCommand.DestinationNode},
                {"SC", GrommetXBee.XBeeCommand.ScanChannels},
                {"SD", GrommetXBee.XBeeCommand.ScanDuration},
                {"ZS", GrommetXBee.XBeeCommand.ZigBeeStackProfile},
                {"NJ", GrommetXBee.XBeeCommand.NodeJoinTime},
                {"JV", GrommetXBee.XBeeCommand.ChannelVerification},
                {"JN", GrommetXBee.XBeeCommand.JoinNotification},
                {"AR", GrommetXBee.XBeeCommand.AggregateRoutingNotification},
                {"AI", GrommetXBee.XBeeCommand.AssociationIndication},
                {"EE", GrommetXBee.XBeeCommand.EncryptionEnable},
                {"EO", GrommetXBee.XBeeCommand.EncryptionOptions},
                {"NK", GrommetXBee.XBeeCommand.EncryptionKey},
                {"KY", GrommetXBee.XBeeCommand.LinkKey},
                {"PL", GrommetXBee.XBeeCommand.PowerLevel},
                {"PM", GrommetXBee.XBeeCommand.PowerMode},
                {"DB", GrommetXBee.XBeeCommand.ReceivedSignalStrength},
                {"AP", GrommetXBee.XBeeCommand.ApiEnable},
                {"AO", GrommetXBee.XBeeCommand.ApiOptions},
                {"BD", GrommetXBee.XBeeCommand.InterfaceDataRate},
                {"NB", GrommetXBee.XBeeCommand.SerialParity},
                {"RO", GrommetXBee.XBeeCommand.PacketizationTimeout},
                {"D7", GrommetXBee.XBeeCommand.Dio7Configuration},
                {"D6", GrommetXBee.XBeeCommand.Dio6Configuration},
                {"IS", GrommetXBee.XBeeCommand.ForceSample},
                {"1S", GrommetXBee.XBeeCommand.XBeeSensorSample},
                {"IR", GrommetXBee.XBeeCommand.IOSampleRate},
                {"IC", GrommetXBee.XBeeCommand.IODigitalChangeDirection},
                {"P0", GrommetXBee.XBeeCommand.Pwm0Configuration},
                {"P1", GrommetXBee.XBeeCommand.Dio11Configuration},
                {"P2", GrommetXBee.XBeeCommand.Dio12Configuration},
                {"P3", GrommetXBee.XBeeCommand.Dio13Configuration},
                {"D0", GrommetXBee.XBeeCommand.ADio0Configuration},
                {"D1", GrommetXBee.XBeeCommand.ADio1Configuration},
                {"D2", GrommetXBee.XBeeCommand.ADio2Configuration},
                {"D3", GrommetXBee.XBeeCommand.ADio3Configuration},
                {"D4", GrommetXBee.XBeeCommand.Dio4Configuration},
                {"D5", GrommetXBee.XBeeCommand.Dio5Configuration},
                {"LT", GrommetXBee.XBeeCommand.AssociateLedBlinkTime},
                {"D8", GrommetXBee.XBeeCommand.Dio8Configuration},
                {"PR", GrommetXBee.XBeeCommand.PullUpRegister},
                {"RP", GrommetXBee.XBeeCommand.RssiPwmTimer},
                {"CB", GrommetXBee.XBeeCommand.CommissioningPushButton},
                {"VR", GrommetXBee.XBeeCommand.FirmwareVersion},
                {"HV", GrommetXBee.XBeeCommand.HardwareVersion},
                {"%V", GrommetXBee.XBeeCommand.SupplyVoltage},
                {"SM", GrommetXBee.XBeeCommand.SleepMode},
                {"SN", GrommetXBee.XBeeCommand.NumberOfSleepPeriods},
                {"SP", GrommetXBee.XBeeCommand.SleepPeriod},
                {"ST", GrommetXBee.XBeeCommand.TimeBeforeSleep},
                {"SO", GrommetXBee.XBeeCommand.SleepOptions}
            };
        }

        #region Class data

        private readonly GrommetXBee.XBeeModule _xbee;
        private Dictionary<string, GrommetXBee.XBeeCommand> _commands;

        #endregion
    }
}
